import torch
from torch.autograd import Variable

"""
自动求导
PyTorch： Variables 和 autograd

在上面的例子中，我们自己实现了神经网络的前向和反向传播。手动实现反向传播对于一个小型的两层网络来说并不是什么难题，但是对于大型复杂啊网络，很快就会变得棘手。
值得庆幸的是，我们可以使用自动微分来自动计算神经网络的反向传播。PyTorch 中的 autograd 包提供了这一功能。
当我们使用自动求导时，你的神经网络的前向传播讲定义一个计算图；图中的节点将是 Tensors，边表示从输入张量产生输出张量的函数。通过图反向传播可以让你轻松的计算梯度。
这听起来很复杂，但是在实际使用却十分简单。我们用 Variable 对象来包装 Tensors。 一个 Variable 表示计算图中的一个节点。
如果 x 是一个 Variable，那么 x.data 则是一个 Tensor，x.grad 是另一个用来保存 x 关于某个标量值的梯度。
PyTorch 的 Variable 拥有和 PyTorch 的 Tensors 一样的 API：几乎所有 Tensors 上的操作都可以在 Variable 上运行；
不同之处在于使用 Variables 定义了一个计算图， 允许你自动的计算梯度。
这里我们使用 Variable 和自动求导来实现我们的两层网络；现在我们不再需要手动地实现反向传播。
"""

# 定义Tensor类型
dtype = torch.FloatTensor
# dtype = torch.cuda.FloatTensor # Uncomment this to run on GPU

# N 批的大小
# D_in 输入的数据纬度
# H 隐藏层纬度
# D_out 输出数据纬度
# N is batch size; D_in is input dimenssion
# H is hidden demenssion; D_out is output dimension
N, D_in, H, D_out = 64, 1000, 100, 10

# 创建随机的张量来保存输入和输出，并把它们放在变量里
# 设置requires_grad=False，表示我们在反向传播时不需要计算它们的梯度
# Create random Tensors to hold input and outpus, and wrap them in Variables.
# Setting requires_grad=False indicates that we do not need to compute gradients
# with respect to these Variables during the backward pass
x = Variable(torch.randn(N, D_in).type(dtype), requires_grad=False)
y = Variable(torch.randn(N, D_out).type(dtype), requires_grad=False)

# 创建随机的张量来保存输入和输出，并把它们放在变量里
# 设置requires_grad=True，表示我们在反向传播时要计算它们的梯度
# Create random Tensors for weights, and wrap them in Variables.
# Setting requires_grad=True indicates that we want to compute gradients with
# respect to these Variables during the backward pass.
w1 = Variable(torch.randn(D_in, H).type(dtype), requires_grad=True)
w2 = Variable(torch.randn(H, D_out).type(dtype), requires_grad=True)

# 学习率
learning_rate = 1e-6

# 开始学习
for t in range(500):
    # 正向传播： 使用变量运算计算预测的y，我们使用精确和相同的运算使用张量进行正向传播计算
    # 我们不需要保留对中间值的引用，因为我不是手工实现反向传播
    # Forward pass: compute predicted y using operations on Variables; these
    # are exactly the same operations we used to compute the forward pass using
    # Tensors, but we do not need to keep references to intermediate values since
    # we are not implementing the backward pass by hand.
    # 矩阵乘积，relu，矩阵乘积
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # 使用变量运算计算和打印损失，现在损失是一个形状(1,)的变量，
    # loss.data是形状(1,)的张量，loss.data[0]是保存损失的标量
    # Compute and print loss using operations on Variables.
    # Now loss is a Variable of shape (1,) and loss.data is a Tensor of shape
    # (1,); loss.data[0] is a scalar value holding the loss.
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.data)

    # 使用autaograd计算反向传播，调用这个方法会为requires_grad=True的变量计算损失梯度
    # 调用后w1.grad和w2.grad将会分别保存w1和w2的梯度
    # Use autograd to compute the backward pass. This call will compute the
    # gradient of loss with respect to all Variables with requires_grad=True.
    # After this call w1.grad and w2.grad will be Variables holding the gradient
    # of the loss with respect to w1 and w2 respectively.
    loss.backward()

    # 使用梯度更新权重，w1.data和w2.data是张量，w1.grad和w2.grad是变量
    # w1.grad.data和w2.grad.data是张量
    # Update weights using gradient descent; w1.data and w2.data are Tensors,
    # w1.grad and w2.grad are Variables and w1.grad.data and w2.grad.data are
    # Tensors.
    w1.data -= learning_rate * w1.grad.data
    w2.data-= learning_rate * w2.grad.data

    # 更新权重后手动清零梯度
    # Manually zero the gradients after updating weights
    w1.grad.data.zero_()
    w2.grad.data.zero_()